package com.fxl.majiang.room;

import com.fxl.majiang.enums.CardTypeEnum;
import com.fxl.majiang.enums.TilesEnum;
import com.fxl.majiang.enums.TilesGroupTypeEnum;
import com.fxl.majiang.exception.CustomException;
import lombok.SneakyThrows;
import org.springframework.util.CollectionUtils;

import javax.smartcardio.Card;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * @author fengxinglei
 * @date 2022/8/2 17:50
 * @description 规则管理
 */
public class RuleManager {

    /**
     *  校验是否能够吃牌
     * @param ownedTiles 自己拥有的手牌
     * @param playedTile 其他玩家刚打出的牌
     * @return 手牌中可以吃这张牌的牌组列表
     */
    public synchronized static List<List<TilesEnum>> getChiItGroupList(List<TilesEnum> ownedTiles, TilesEnum playedTile) {
        // 打出的手牌类型
        CardTypeEnum type = playedTile.getType();
        // 如果是万条筒才可以吃
        if (type != CardTypeEnum.WAN && type != CardTypeEnum.TIAO && type != CardTypeEnum.TONG) {
            return new ArrayList<>();
        }
        // 获取可以吃这张牌的牌组列表
        List<List<TilesEnum>> chiItGroupList = TilesEnum.getChiItGroupList(playedTile);
        if (CollectionUtils.isEmpty(chiItGroupList)) {
            return new ArrayList<>();
        }
        // 手牌中可以吃的牌组列表
        List<List<TilesEnum>> ownedGroupList = chiItGroupList.stream()
                // 如果手牌中有对应牌组，则说明可以拿这组牌吃
                .filter(group -> ownedTiles.containsAll(
                        group.stream()
                                .filter(tile -> !tile.equals(playedTile))
                                .collect(Collectors.toList()))
                ).collect(Collectors.toList());
        return ownedGroupList;
    }

    /**
     *  校验是否能够吃牌
     * @param player 玩家信息
     * @param it 要吃的牌
     * @return 手牌中可以吃这张牌的牌组列表
     */
    @SneakyThrows
    public synchronized static List<List<TilesEnum>> getCanChiItGroups(Player player, TilesEnum it, List<TilesEnum> versatileTiles) {
        // 打出的手牌类型
        CardTypeEnum type = it.getType();
        // 如果是万条筒才可以吃
        if (type != CardTypeEnum.WAN && type != CardTypeEnum.TIAO && type != CardTypeEnum.TONG) {
            return new ArrayList<>();
        }
        // 获取可以吃这张牌的牌组
        List<List<TilesEnum>> canChiItGroupList = TilesEnum.getChiItGroupList(it);
        List<List<TilesEnum>> ownedChiItGroup = new ArrayList<>();
        // 判断用户选择的牌组是否在
        for (List<TilesEnum> group : canChiItGroupList) {
            // 可以吃这张牌的另外两张牌
            List<TilesEnum> selectTiles = group.stream().filter(tile -> !tile.equals(it)).collect(Collectors.toList());
            // 判断这两张牌是否有百搭牌，如果有，则不能吃
            boolean haveVersatile = false;
            for (TilesEnum sTile : selectTiles) {
                if (versatileTiles.contains(sTile)) {
                    haveVersatile = true;
                    break;
                }
            }
            if (haveVersatile) {
                continue;
            }
            // 是否与当前用户选择的牌型匹配
            if (player.hasOwnedTiles(selectTiles)) {
                ownedChiItGroup.add(group);
            }
        }
        return ownedChiItGroup;
    }

    /**
     *  校验是否能够吃牌
     * @param player 玩家信息
     * @param selectedGroup 选择的牌组
     * @param it 要吃的牌
     * @return 手牌中可以吃这张牌的牌组列表
     */
    @SneakyThrows
    public synchronized static boolean canChiIt(Player player, TilesEnum it, List<TilesEnum> selectedGroup, List<TilesEnum> versatileTiles) {
        // 打出的手牌类型
        CardTypeEnum type = it.getType();
        // 如果是万条筒才可以吃
        if (type != CardTypeEnum.WAN && type != CardTypeEnum.TIAO && type != CardTypeEnum.TONG) {
            return false;
        }
        // 判断用户是否拥有该手牌，防止老六
        if (!player.hasOwnedTiles(selectedGroup)) {
            return false;
        }
        // 获取可以吃这张牌的牌组
        List<List<TilesEnum>> canChiItGroupList = TilesEnum.getChiItGroupList(it);
        // 判断用户选择的牌组是否在
        for (List<TilesEnum> group : canChiItGroupList) {
            // 可以吃这张牌的另外两张牌
            List<TilesEnum> selectTiles = group.stream().filter(tile -> !tile.equals(it)).collect(Collectors.toList());
            // 判断这两张牌是否有百搭牌，如果有，则不能吃
            boolean haveVersatile = false;
            for (TilesEnum sTile : selectTiles) {
                if (versatileTiles.contains(sTile)) {
                    haveVersatile = true;
                    break;
                }
            }
            if (haveVersatile) {
                continue;
            }
            // 是否与当前用户选择的牌型匹配
            if (group.containsAll(selectedGroup)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 检验是否可以碰这张牌
     * @param player
     * @param it
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canPengIt(Player player, TilesEnum it) {
        // 打出的手牌类型
        CardTypeEnum type = it.getType();
        // 黑花、红花是不能碰的，休想当老六
        if (type == CardTypeEnum.HEI_HUA || type == CardTypeEnum.HONG_HUA) {
            return false;
        }
        // 判断用户是否拥有该手牌，防止老六
        if (player.getOwnedThisTileCount(it) < 2) {
            return false;
        }
        return true;
    }

    /**
     * 判断抓到这张牌是否可以补花
     * @param tile
     * @return
     */
    public synchronized static boolean canSupplement(TilesEnum tile) {
        CardTypeEnum type = tile.getType();
        return type == CardTypeEnum.BAI_BAN || type == CardTypeEnum.HEI_HUA || type == CardTypeEnum.HONG_HUA;
    }

    /**
     * 判断抓到的这些牌是否都可以补花
     * @param tiles
     * @return
     */
    public synchronized static boolean canSupplementAll(List<TilesEnum> tiles) {
        for (TilesEnum tile : tiles) {
            // 只要有一张不能补，则返回false
            if (!canSupplement(tile)) {
                return false;
            }
        }
        return true;
    }

    /**
     * 判断是否可以明杠这张牌
     * 手头有四张牌，不管是已经碰过的，还是在手牌里的，都可以选择明杠
     * @param tile
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canMingGangIt(Player player, TilesEnum tile) {
        CardTypeEnum type = tile.getType();
        // 如果是黑花、红花、白板，不能杠
        if (type == CardTypeEnum.HEI_HUA || type == CardTypeEnum.HONG_HUA || type == CardTypeEnum.BAI_BAN) {
            return false;
        }
        // 只要对上四个一样的就可以明杠
        // 先检查手牌中有没有三个一样的
        long count = player.getOwnedThisTileCount(tile);
        if (count == 3) {
            return true;
        }
//        // 如果手牌中没有，查看是否有碰过这张牌，如果有，则可以明杠
//        if (player.hasPeng(tile)) {
//            return true;
//        }
        return false;
    }

    /**
     * 判断抓牌之后是否可以明杠这张牌
     * 手头有四张牌，不管是已经碰过的，还是在手牌里的，都可以选择明杠
     * @param tile
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canMingGangItAfterGrabbing(Player player, TilesEnum tile) {
        CardTypeEnum type = tile.getType();
        // 如果是黑花、红花、白板，不能杠
        if (type == CardTypeEnum.HEI_HUA || type == CardTypeEnum.HONG_HUA || type == CardTypeEnum.BAI_BAN) {
            return false;
        }
        if (!player.hasOwnedTile(tile)) {
            return false;
        }
        // 查看是否有碰过这张牌，如果有，则可以明杠
        if (player.hasPeng(tile)) {
            return true;
        }
        return false;
    }

    /**
     * 判断是否可以暗杠这张牌
     * 只有手牌中有四张牌，才可以暗杠 ，这里的暗杠不包括花，如果要杠花，走下边 canHuaAnGang
     * @param tile
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canAnGang(Player player, TilesEnum tile) {
        CardTypeEnum type = tile.getType();
        // 如果是黑花、红花、白板,不能暗杠
        if (type == CardTypeEnum.HEI_HUA || type == CardTypeEnum.HONG_HUA || type == CardTypeEnum.BAI_BAN) {
            return false;
        }
        // 检查手牌中有没有四个一样的
        return player.getOwnedThisTileCount(tile) == 4;
    }

    /**
     * 花暗杠，只能起手牌暗杠
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canHuaAnGang(Player player, CardTypeEnum type) {
        // 如果是黑花、红花
        if (type == CardTypeEnum.HEI_HUA || type == CardTypeEnum.HONG_HUA) {
            // 如果起手手牌有该花色所有的牌，则可以杠
            return player.hasOwnedTiles(TilesEnum.getByType(type));
        }
        // 如果是白板
        else if (type == CardTypeEnum.BAI_BAN) {
            // 判断是否有四张白板
            return player.getOwnedThisTileCount(TilesEnum.BAI_BAN) == 4;
        }
        return false;
    }

    /**
     * 自摸
     * @param player
     * @param versatileTiles
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canZiMo(Player player, List<TilesEnum> versatileTiles) {
        // 只需要计算手牌即可
        List<TilesEnum> ownedTiles = player.getOwnedTiles();
        // 是否杀猪胡
        if (canShaZhu(ownedTiles, versatileTiles)) {
            return true;
        }
        // 是否花湖
        if (canHuaHu(ownedTiles, player.getOwnedHuaTilesGroup(),versatileTiles)) {
            return true;
        }
        List<TilesEnum> huaTiles = ownedTiles.stream()
                .filter(tile -> tile.getType() == CardTypeEnum.BAI_BAN
                        || tile.getType() == CardTypeEnum.HONG_HUA
                        || tile.getType() == CardTypeEnum.BAI_BAN).collect(Collectors.toList());
        // 手上有花牌，不能胡
        if (!CollectionUtils.isEmpty(huaTiles)) {
            return false;
        }

        return canHu(ownedTiles, versatileTiles);
    }

    /**
     * 是否花胡
     * @param ownedTiles
     * @param ownedHuaTilesGroup
     * @return
     */
    public synchronized static boolean canHuaHu(List<TilesEnum> ownedTiles, List<TilesGroup> ownedHuaTilesGroup, List<TilesEnum> versatileTiles) {
        // 如果百搭牌包含黑花或者红花，则必定不能花胡
        for (TilesEnum vTile: versatileTiles) {
            if (vTile.getType() == CardTypeEnum.HEI_HUA || vTile.getType() == CardTypeEnum.HONG_HUA) {
                return false;
            }
        }
        List<TilesEnum> heiHuaTiles = new ArrayList<>();
        List<TilesEnum> hongHuaTiles = new ArrayList<>();
        for (TilesEnum tile : ownedTiles) {
            if (tile.getType() == CardTypeEnum.HEI_HUA) {
                heiHuaTiles.add(tile);
            } else if (tile.getType() == CardTypeEnum.HONG_HUA) {
                hongHuaTiles.add(tile);
            }
        }
        for (TilesGroup group : ownedHuaTilesGroup) {
            if (group.getType() == TilesGroupTypeEnum.HEI_HUA) {
                heiHuaTiles.addAll(group.getTiles());
            } else if (group.getType() == TilesGroupTypeEnum.HONG_HUA) {
                hongHuaTiles.addAll(group.getTiles());
            }
        }
        if (heiHuaTiles.containsAll(TilesEnum.getByType(CardTypeEnum.HEI_HUA)) && hongHuaTiles.containsAll(TilesEnum.getByType(CardTypeEnum.HONG_HUA))) {
            return true;
        }
        return false;
    }

    /**
     * 点炮
     * @param player
     * @param it
     * @return
     */
    @SneakyThrows
    public synchronized static boolean dianPao(Player player, TilesEnum it, List<TilesEnum> versatileTiles) {
        // 只需要计算手牌即可
        List<TilesEnum> ownedTiles = player.getOwnedTiles();
        List<TilesEnum> ownedTilesWithIt = new ArrayList<>(ownedTiles);
        ownedTilesWithIt.add(it);
        return canHu(ownedTilesWithIt, versatileTiles);
    }

    /**
     * 判断是否 杀猪胡((>=3)百搭1 + N(>=1)百搭2)
     * @return
     */
    public synchronized static boolean canShaZhu(List<TilesEnum> ownedTiles, List<TilesEnum> versatileTiles) {
        // 拥有的百搭牌
        List<TilesEnum> ownedVersatileTiles = ownedTiles.stream()
                .filter(tile -> versatileTiles.contains(tile)).collect(Collectors.toList());
        // 首先百搭个数必须是四个以上
        if (CollectionUtils.isEmpty(ownedVersatileTiles) || ownedVersatileTiles.size() < 4) {
            return false;
        }
        // 统计每个百搭的数量
        Map<TilesEnum, Long> tilesCountMap = ownedVersatileTiles.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        // 必须同时有两种百搭牌
        if (tilesCountMap.keySet().size() != 2) {
            return false;
        }
        // 只要其中一种百搭牌数量是三个就行
        for (Long count : tilesCountMap.values()) {
            if (count.intValue() >= 3) {
                return true;
            }
        }
        return false;
    }

    /**
     * 是否可以胡牌，这里没有计算胡的牌型，只是按照普通的规则计算了
     * @param ownedTiles 拥有的手牌
     * @param versatileTiles 百搭牌
     * @return
     */
    public synchronized static boolean canHu(List<TilesEnum> ownedTiles, List<TilesEnum> versatileTiles) {
        // 判断是否剩下最后两张将牌了
        if (ownedTiles.size() == 2) {
            // 两张手牌一样则可以胡
            if (ownedTiles.get(0) == ownedTiles.get(1)) {
                return true;
            }
            // 或者有任意一张百搭牌也可以胡
            else if (versatileTiles.contains(ownedTiles.get(0)) || versatileTiles.contains(ownedTiles.get(1))) {
                return true;
            }
        }
        // 去除百搭牌后的手牌
        List<TilesEnum> tilesWithOutVersatile = new ArrayList<>();
        // 拥有的百搭牌
        List<TilesEnum> ownedVersatileTiles = new ArrayList<>();
        ownedTiles.stream().forEach(tile -> {
            if (versatileTiles.contains(tile)) {
                ownedVersatileTiles.add(tile);
            } else {
                tilesWithOutVersatile.add(tile);
            }
        });

        // 万牌
        List<TilesEnum> wanList = new ArrayList<>();
        // 条牌
        List<TilesEnum> tiaoList = new ArrayList<>();
        // 筒牌
        List<TilesEnum> tongList = new ArrayList<>();
        // 风牌
        List<TilesEnum> fengList = new ArrayList<>();
        // 字牌
        List<TilesEnum> ziList = new ArrayList<>();
        tilesWithOutVersatile.stream().forEach(tile -> {
            CardTypeEnum type = tile.getType();
            if (type == CardTypeEnum.WAN) {
                wanList.add(tile);
            } else if (type == CardTypeEnum.TIAO) {
                tiaoList.add(tile);
            } else if (type == CardTypeEnum.TONG) {
                tongList.add(tile);
            } else if (type == CardTypeEnum.FENG) {
                fengList.add(tile);
            } else if (type == CardTypeEnum.ZI) {
                ziList.add(tile);
            }
        });
        // 胡牌牌组数据
        HuData huData = new HuData();
        // 校验风牌
        if (!CollectionUtils.isEmpty(fengList) && !checkFengOrZiWhenHu(huData, fengList, ownedVersatileTiles)) {
            return false;
        }
        // 校验字牌
        if (!CollectionUtils.isEmpty(ziList) && !checkFengOrZiWhenHu(huData, ziList, ownedVersatileTiles)) {
            return false;
        }
        // 校验万牌
        if (!CollectionUtils.isEmpty(wanList) && !checkWanTiaoTong(huData, wanList, ownedVersatileTiles)) {
            return false;
        }
        // 校验条牌
        if (!CollectionUtils.isEmpty(tiaoList) && !checkWanTiaoTong(huData, tiaoList, ownedVersatileTiles)) {
            return false;
        }
        // 校验筒牌
        if (!CollectionUtils.isEmpty(tongList) && !checkWanTiaoTong(huData, tongList, ownedVersatileTiles)) {
            return false;
        }
        // 通过了以上所有校验，才能胡牌
        return true;
    }

    /**
     * 检查 万条筒牌型是否能胡
     * @param huData
     * @param tiles
     * @param ownedVersatileTiles
     * @return
     */
    private synchronized static boolean checkWanTiaoTong(HuData huData, List<TilesEnum> tiles, List<TilesEnum> ownedVersatileTiles) {
        CardTypeEnum type = tiles.get(0).getType();
        List<TilesEnum> tempTiles = new ArrayList<>(tiles);
        // ddd的牌组
        List<TilesEnum> dddTiles = new ArrayList<>();
        // 统计每张牌的张数
        Map<TilesEnum, Long> tilesCountMap = tempTiles.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        for (TilesEnum tile : tilesCountMap.keySet()) {
            // 一旦出现DDD牌型，则将该牌暂时认定为ddd牌组
            if (tilesCountMap.get(tile) >= 3) {
                dddTiles.add(tile);
                tempTiles.remove(tile);
                tempTiles.remove(tile);
                tempTiles.remove(tile);
            }
        }
        // 判断是否还有剩余手牌，有则继续校验，没有则说明可以搭起来
        if (tempTiles.size() == 0) {
            return true;
        }
        // 接着校验除去DDD手牌后的ABC牌组
        // 所有abc组合牌
        List<List<TilesEnum>> allAbcGroupList = TilesEnum.getABCGroupList(type);
        // 手牌中的abc牌组
        List<List<TilesEnum>> abcGroupList = new ArrayList<>();
        // 判断剩余手牌中是否拥有abc牌组的牌
        // 首先筛选三张都为单张的
        for (List<TilesEnum> abcGroup : allAbcGroupList) {
            if (tempTiles.containsAll(abcGroup)
                    && tilesCountMap.get(abcGroup.get(0)).intValue() == 1
                    && tilesCountMap.get(abcGroup.get(1)).intValue() == 1
                    && tilesCountMap.get(abcGroup.get(2)).intValue() == 1 ) {
                // 加入abc牌组
                abcGroupList.add(abcGroup);
                // 从手牌中移除
                tempTiles.remove(abcGroup.get(0));
                tempTiles.remove(abcGroup.get(1));
                tempTiles.remove(abcGroup.get(2));
            }
        }
        // 判断是否还有剩余手牌，有则说明DDD手牌中可能有需要拿出来做abc搭子的，或者其他情况组合，需要继续校验，没有则说明可以搭起来
        if (tempTiles.size() == 0) {
            return true;
        }
        // 再筛选其他不为单张的
        for (List<TilesEnum> abcGroup : allAbcGroupList) {
            if (tempTiles.containsAll(abcGroup)) {
                // 加入abc牌组
                abcGroupList.add(abcGroup);
                // 从手牌中移除
                tempTiles.remove(abcGroup.get(0));
                tempTiles.remove(abcGroup.get(1));
                tempTiles.remove(abcGroup.get(2));
            }
        }
        // 接着判断手牌中是否有只缺一张能够组成abc牌组的牌
        // 对abc牌组循环2遍, 避免出现重复牌组，比如一万二万三万，一万二万三万
        for (int i = 0 ; i < 2; i++) {
            // 一旦有abc缺少一个的牌组，则说明牌组不能胡
            boolean buildResult = buildAbcGroup(tempTiles, allAbcGroupList, dddTiles, abcGroupList, ownedVersatileTiles);
            if (!buildResult) {
                return false;
            }
        }
        // 判断是否还有剩余手牌，如果还有，则继续校验
        if (tempTiles.size() == 0) {
            return true;
        }
        // 注：由于ddd牌型在上边都已经使用过了，剩下的牌中不可能在次出现ddd牌组了，顶多只有两张
        // 判断是否已经有将牌，如果有的话，则不能再当将牌了，必须凑齐3张的搭子，如果没有将牌，则可以拿其中一个来凑一对将牌
        // 再次统计每张牌的数量
        tilesCountMap = tempTiles.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        // 需要的搭子数量
        int needVersatileCount = 0;
        for (TilesEnum tile : tilesCountMap.keySet()) {
            int count = tilesCountMap.get(tile).intValue();
            // 如果这张牌剩余一张，则要么做将牌，要么找搭子凑齐三张
            if (count == 1) {
                if (!huData.haveJiang()) {
                    // 如果搭子不够，则胡不了
                    if (CollectionUtils.isEmpty(ownedVersatileTiles)) {
                        return false;
                    } else {// 找一张搭子凑
                        List<TilesEnum> jiangList = new ArrayList<>();
                        jiangList.add(tile);
                        jiangList.add(ownedVersatileTiles.get(0));
                        huData.setJiang(jiangList);
                        ownedVersatileTiles.remove(0);
                    }
                } else { // 如果已经有将牌了，则找搭子凑三张
                    if (CollectionUtils.isEmpty(ownedVersatileTiles) || ownedVersatileTiles.size() < 2) {
                        return false;
                    } else {
                        List<TilesEnum> dddList = new ArrayList<>();
                        dddList.add(tile);
                        dddList.add(ownedVersatileTiles.get(0));
                        dddList.add(ownedVersatileTiles.get(1));
                        huData.addToDDDGroup(dddList);
                        ownedVersatileTiles.remove(0);
                        ownedVersatileTiles.remove(0);
                    }
                }
            } else { // 如果剩余两张，则要么做将牌，要么找搭子凑三张
                if(!huData.haveJiang()) {
                    List<TilesEnum> jiangList = new ArrayList<>();
                    jiangList.add(tile);
                    jiangList.add(tile);
                    huData.setJiang(jiangList);
                } else {// 如果不需要将牌，则必须凑齐三张
                    if (CollectionUtils.isEmpty(ownedVersatileTiles)) {
                        return false;
                    } else {
                        List<TilesEnum> dddList = new ArrayList<>();
                        dddList.add(tile);
                        dddList.add(tile);
                        dddList.add(ownedVersatileTiles.get(0));
                        huData.addToDDDGroup(dddList);
                        ownedVersatileTiles.remove(0);
                    }
                }
            }
//            if (!huData.haveJiang()) {
//                // 如果没有将牌，则这张拿来当将牌
//                needVersatileCount += 2 - count;
//                // 判断搭子是否足够,只有搭子足够才能胡
//                if (ownedVersatileTiles.size() < needVersatileCount) {
//                    return false;
//                } else {
//                    List<TilesEnum> jiangList = new ArrayList<>();
//                    jiangList.add(tile);
//                    jiangList.add(ownedVersatileTiles.get(0));
//                    huData.setJiang(jiangList);
//                    ownedVersatileTiles.remove(0);
//                }
//            } else {
//                // 如果有将牌，则这个牌只能搭齐三张才行
//                needVersatileCount += 3 - count;
//                // 判断搭子是否足够,只有搭子足够才能胡
//                if (ownedVersatileTiles.size() < needVersatileCount) {
//                    return false;
//                } else {
//                    List<TilesEnum> dddList = new ArrayList<>();
//                    dddList.add(tile);
//                    dddList.add(ownedVersatileTiles.get(0));
//                    dddList.add(ownedVersatileTiles.get(1));
//                    huData.addToDDDGroup(dddList);
//                    ownedVersatileTiles.remove(0);
//                    ownedVersatileTiles.remove(1);
//                }
//            }
        }
        return true;
    }

    /**
     * 校验abc牌组
     * @param tempTiles 剩余手牌临时变量
     * @param allAbcGroupList 所有abc牌组
     * @param dddTiles 最终胡的ddd牌的牌组
     * @param abcGroupList 最终胡abc牌组
     * @param ownedVersatileTiles 拥有的百搭牌
     * @return
     */
    private synchronized static boolean buildAbcGroup(List<TilesEnum> tempTiles, List<List<TilesEnum>> allAbcGroupList, List<TilesEnum> dddTiles, List<List<TilesEnum>> abcGroupList, List<TilesEnum> ownedVersatileTiles) {
        for (List<TilesEnum> abcGroup : allAbcGroupList) {
            // abc缺一张的牌组
            List<TilesEnum> abcMissingOne = new ArrayList<>();
            // 缺的那张牌
            TilesEnum theMissingOne = null;
            if (tempTiles.contains(abcGroup.get(0)) && tempTiles.contains(abcGroup.get(1)) && !tempTiles.contains(abcGroup.get(2))) {
                abcMissingOne.add(abcGroup.get(0));
                abcMissingOne.add(abcGroup.get(1));
                theMissingOne = abcGroup.get(2);
            } else if (tempTiles.contains(abcGroup.get(0)) && tempTiles.contains(abcGroup.get(2)) && !tempTiles.contains(abcGroup.get(1))) {
                abcMissingOne.add(abcGroup.get(0));
                abcMissingOne.add(abcGroup.get(2));
                theMissingOne = abcGroup.get(1);
            } else if (tempTiles.contains(abcGroup.get(1)) && tempTiles.contains(abcGroup.get(2)) && !tempTiles.contains(abcGroup.get(0))) {
                abcMissingOne.add(abcGroup.get(1));
                abcMissingOne.add(abcGroup.get(2));
                theMissingOne = abcGroup.get(0);
            }
            // 如果有abc缺一张的牌组
            if (abcMissingOne.size() == 2) {
                // 如果缺的这张在ddd牌组中，则从中取出
                if (dddTiles.contains(theMissingOne)) {
                    // 从ddd牌组移除
                    dddTiles.remove(theMissingOne);
                    // 将这三张牌重新加入手牌中
                    tempTiles.add(theMissingOne);
                    tempTiles.add(theMissingOne);
                    tempTiles.add(theMissingOne);
                    // 加入abc牌组
                    abcGroupList.add(abcGroup);
                    // 移除这组abc牌组的牌
                    tempTiles.remove(abcGroup.get(0));
                    tempTiles.remove(abcGroup.get(1));
                    tempTiles.remove(abcGroup.get(2));
                }
                // 如果缺的牌手上没有，则尝试使用百搭搭配起三张
                else {
                    if (!CollectionUtils.isEmpty(ownedVersatileTiles)) {
                        // 加入一张百搭，凑齐abc牌组
                        List<TilesEnum> abcGroupWithVersatile = new ArrayList<>();
                        abcGroupWithVersatile.addAll(abcMissingOne);
                        abcGroupWithVersatile.add(ownedVersatileTiles.get(0));
                        abcGroupList.add(abcGroupWithVersatile);
                        // 移除这两张abc牌组的牌
                        tempTiles.remove(abcMissingOne.get(0));
                        tempTiles.remove(abcMissingOne.get(1));
                        // 移除这张百搭牌
                        ownedVersatileTiles.remove(0);
                    }
                    // 如果百搭不够，则说明没法胡
                    else {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 校验风牌和字牌是否符合胡的牌型
     * @param huData
     * @param tiles
     * @param ownedVersatileTiles
     */
    private synchronized static boolean checkFengOrZiWhenHu(HuData huData, List<TilesEnum> tiles, List<TilesEnum> ownedVersatileTiles) {
        // 统计每张风Or字牌张数
        Map<TilesEnum, Long> tilesCountMap = tiles.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        // 判断风Or字牌是否凑齐DDD*N + ee牌型
        for (TilesEnum tile : tilesCountMap.keySet()) {
            int count = tilesCountMap.get(tile).intValue();
            if (count == 4) {
                // 风Or字牌如果有四张，必须杠掉才行, 否则直接不能胡
                return false;
            } else if (count == 3) {
                // 如果风Or字有三张，则加入最终胡牌牌组
                List<TilesEnum> group = new ArrayList<>();
                group.add(tile);
                group.add(tile);
                group.add(tile);
                huData.addToDDDGroup(group);
            } else if (count == 2) {
                // 如果有两张风Or字牌，首先判断是否有将，优先做将牌，如果没有将牌，则尝试配上一张百搭是否能够搭起三张，如果百搭不够，则没法胡
                if (!huData.haveJiang()) {
                    List<TilesEnum> group = new ArrayList<>();
                    group.add(tile);
                    group.add(tile);
                    // 加入胡牌牌组
                    huData.addToDDDGroup(group);
                    huData.setJiang(group);
                } else {
                    // 尝试配上一张百搭凑齐三张
                    if (!CollectionUtils.isEmpty(ownedVersatileTiles)) {
                        List<TilesEnum> group = new ArrayList<>();
                        group.add(tile);
                        group.add(tile);
                        // 百搭牌用作牌组
                        group.add(ownedVersatileTiles.get(0));
                        // 移除一张百搭牌
                        ownedVersatileTiles.remove(0);
                        // 加入胡牌牌组
                        huData.addToDDDGroup(group);
                    }
                    // 如果百搭不够，则没法胡
                    else {
                        return false;
                    }
                }
            }
            // 如果某个风牌只有一张，则判断是否需要将牌, 如果需要，则尝试配合上百搭作为将牌，如果不需要将牌，则需要额外两张百搭凑齐三张才行
            else if (count == 1) {
                // 是否需要将牌
                if (huData.haveJiang()) {
                    // 配合上两张百搭凑齐DDD牌组
                    if (!CollectionUtils.isEmpty(ownedVersatileTiles) && ownedVersatileTiles.size() >= 2) {
                        List<TilesEnum> group = new ArrayList<>();
                        group.add(tile);
                        group.add(ownedVersatileTiles.get(0));
                        group.add(ownedVersatileTiles.get(1));
                        // 移除两张百搭牌
                        ownedVersatileTiles.remove(0);
                        ownedVersatileTiles.remove(0);
                        // 加入胡牌牌组
                        huData.addToDDDGroup(group);
                    }
                    // 如果百搭不够，则没法胡
                    else {
                        return false;
                    }
                }
                // 如果没将牌，则尝试配合百搭当做将牌使用
                else {
                    if (!CollectionUtils.isEmpty(ownedVersatileTiles)) {
                        List<TilesEnum> group = new ArrayList<>();
                        group.add(tile);
                        group.add(ownedVersatileTiles.get(0));
                        ownedVersatileTiles.remove(0);
                    }
                    // 如果百搭不够，则没法胡
                    else {
                        return false;
                    }
                }
            }
        }
        return true;
    }

    /**
     * 是否可以胡这张牌
     * @param player
     * @param it
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canDianPaoIt(Player player, TilesEnum it) {
        Room room = RoomManager.getUserRoom(player.getUserId());
        if (room == null) {
            throw new CustomException("你不在该房间,不要当老六");
        }
        // 首先判断是否能够碰碰胡
        if (canPengPengHuIt(player,it)) {
            return true;
        }
        // 校验是否能普通胡
        List<TilesEnum> ownedTiles = new ArrayList<>(player.getOwnedTiles());
        ownedTiles.add(it);
        if (canHu(ownedTiles, room.getDealers().getVersatileTiles())) {
            return true;
        }
        return false;
    }

    /**
     * 是否可以碰碰胡这张牌
     * @param player
     * @param it
     * @return
     */
    @SneakyThrows
    public synchronized static boolean canPengPengHuIt(Player player, TilesEnum it) {
        Room room = RoomManager.getUserRoom(player.getUserId());
        if (room == null) {
            throw new CustomException("你不在该房间,不要当老六");
        }
        List<TilesEnum> ownedTiles = new ArrayList<>(player.getOwnedTiles());
        ownedTiles.add(it);
        List<TilesEnum> versatileTiles = new ArrayList<>(room.getDealers().getVersatileTiles());
        return canPengPengHu(ownedTiles, player.getOwnedTilesGroup(), versatileTiles);
    }

    /**
     * 判断牌组为 ddd + ddd + ddd + ddd + ddd + ee 组合 (百搭当普通牌，也就是不包含百搭汇与杀猪胡)
     * @param ownedTiles 拥有的手牌
     * @param ownedTilesGroup 旁边吃/碰/杠过的牌组
     * @param versatileTiles 百搭牌
     */
    public synchronized static boolean canPengPengHu(List<TilesEnum> ownedTiles, List<TilesGroup> ownedTilesGroup, List<TilesEnum> versatileTiles) {
        // 判断旁边牌组中是否有吃牌，如果有吃牌，则肯定不能碰碰胡
        for (TilesGroup group : ownedTilesGroup) {
            TilesGroupTypeEnum type = group.getType();
            if (type == TilesGroupTypeEnum.CHI) {
                return false;
            }
        }
        // 去除百搭牌后的手牌
        List<TilesEnum> tilesWithOutVersatile = new ArrayList<>();
        // 拥有的百搭牌
        List<TilesEnum> ownedVersatileTiles = new ArrayList<>();
        ownedTiles.stream().forEach(tile -> {
            if (versatileTiles.contains(tile)) {
                ownedVersatileTiles.add(tile);
            } else {
                tilesWithOutVersatile.add(tile);
            }
        });
        // 需要的刻子数量
        int needKeZiCount = ownedTiles.size() / 3;
        // 记录所有牌的张数
        Map<TilesEnum, Long> tilesCountMap = tilesWithOutVersatile.stream().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
        // 刻子牌组数量
        int keZiCount = 0;
        // 查找所有牌的张数，一旦有>=3张，就取3张出来当刻子
        for (TilesEnum tile : tilesCountMap.keySet()) {
            if (tilesCountMap.get(tile).intValue() >= 3) {
                // 移除三张该牌
                for (int i = 0; i<3; i++) {
                    tilesWithOutVersatile.remove(tile);
                }
                keZiCount++;
            }
        }
        // 判断是否凑齐该有的刻子数量了, 如果为凑齐，则继续用剩余手牌, 则通过搭子搭上看看是否可以凑齐
        if (keZiCount < needKeZiCount && !CollectionUtils.isEmpty(ownedVersatileTiles)) {
            for (TilesEnum tile : tilesCountMap.keySet()) {
                // 先凑有2张的， 而且必须还剩1张百搭牌，否则没法搭了
                if (tilesCountMap.get(tile) == 2 && ownedVersatileTiles.size() >= 1) {
                    // 移除2张该牌
                    for (int i = 0; i < 2; i++) {
                        tilesWithOutVersatile.remove(tile);
                    }
                    // 移除一张百搭牌
                    ownedVersatileTiles.remove(0);
                    keZiCount++;
                }
            }
        }
        // 判断是否凑齐所有刻子了, 如果为凑齐，则继续用剩余手牌, 则通过搭子搭上看看是否可以凑齐
        if (keZiCount < needKeZiCount && !CollectionUtils.isEmpty(ownedVersatileTiles)) {
            for (TilesEnum tile : tilesCountMap.keySet()) {
                // 这次凑只有1张的，百搭牌必须还剩2张以上
                if (tilesCountMap.get(tile) == 1 && ownedVersatileTiles.size() >= 2) {
                    // 移除该牌
                    tilesWithOutVersatile.remove(tile);
                    // 移除一张百搭牌
                    ownedVersatileTiles.remove(0);
                    ownedVersatileTiles.remove(0);
                    keZiCount++;
                }
            }
        }

        // 如果拼出了所有刻子，则继续校验将牌
        if (keZiCount == needKeZiCount) {
            // 如果 手牌没了，但是剩两个百搭，可以胡
            if (tilesWithOutVersatile.size() == 0 && ownedVersatileTiles.size() == 2) {
                return true;
            }
            // 如果还剩一张手牌和一个搭子，也可以胡
            else if (tilesWithOutVersatile.size() == 1 && ownedVersatileTiles.size() == 1) {
                return true;
            }
            // 或者如果两张牌一样的话，也可以胡这种牌型了
            else if (tilesWithOutVersatile.size() == 2) {
                return tilesWithOutVersatile.get(0) == tilesWithOutVersatile.get(1);
            }
            // 其他情况均看做不胡
            return false;
        }
        return false;
    }

    public static void main(String[] args) {
//        List<TilesEnum> ownedTiles = new ArrayList<>();
//        ownedTiles.add(TilesEnum.YI_WAN);
//        ownedTiles.add(TilesEnum.YI_WAN);
//        ownedTiles.add(TilesEnum.YI_WAN);
//        ownedTiles.add(TilesEnum.ER_WAN);
//        ownedTiles.add(TilesEnum.ER_WAN);
//        ownedTiles.add(TilesEnum.ER_WAN);
//        ownedTiles.add(TilesEnum.SAN_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//        ownedTiles.add(TilesEnum.QI_WAN);
////        ownedTiles.add(TilesEnum.SI_WAN);
////        ownedTiles.add(TilesEnum.SI_WAN);
////        ownedTiles.add(TilesEnum.SI_WAN);
////        ownedTiles.add(TilesEnum.WU_WAN);
////        ownedTiles.add(TilesEnum.WU_WAN);
////        ownedTiles.add(TilesEnum.WU_WAN);
//        ownedTiles.add(TilesEnum.JIU_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//        List<TilesEnum> versatileTiles = new ArrayList<>();
//        versatileTiles.add(TilesEnum.QI_WAN);
//        versatileTiles.add(TilesEnum.BA_WAN);
//        List<TilesGroup> ownedGroups = new ArrayList<>();
//        List<TilesEnum> group1 = new ArrayList<>();
//        group1.add(TilesEnum.WU_WAN);
//        group1.add(TilesEnum.WU_WAN);
//        group1.add(TilesEnum.WU_WAN);
//        List<TilesEnum> group2 = new ArrayList<>();
//        group2.add(TilesEnum.YI_TIAO);
//        group2.add(TilesEnum.YI_TIAO);
//        group2.add(TilesEnum.YI_TIAO);
//        ownedGroups.add(new TilesGroup(TilesGroupTypeEnum.PENG, group1));
//        ownedGroups.add(new TilesGroup(TilesGroupTypeEnum.PENG, group2));
//        boolean result = canPengPengHu(ownedTiles, ownedGroups,versatileTiles);
//        System.err.println(result);

//        List<TilesEnum> ownedTiles = new ArrayList<>();
//        ownedTiles.add(TilesEnum.YI_WAN);
//        ownedTiles.add(TilesEnum.YI_WAN);
//        ownedTiles.add(TilesEnum.YI_WAN);
//        ownedTiles.add(TilesEnum.ER_WAN);
//        ownedTiles.add(TilesEnum.ER_WAN);
//        ownedTiles.add(TilesEnum.ER_WAN);
//        ownedTiles.add(TilesEnum.SAN_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//        ownedTiles.add(TilesEnum.QI_WAN);
////        ownedTiles.add(TilesEnum.SI_WAN);
////        ownedTiles.add(TilesEnum.SI_WAN);
////        ownedTiles.add(TilesEnum.SI_WAN);
////        ownedTiles.add(TilesEnum.WU_WAN);
////        ownedTiles.add(TilesEnum.WU_WAN);
////        ownedTiles.add(TilesEnum.WU_WAN);
//        ownedTiles.add(TilesEnum.JIU_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//        List<TilesGroup> ownedHuaGroups = new ArrayList<>();
//        List<TilesEnum> heiHuaList = new ArrayList<>();
//        heiHuaList.add(TilesEnum.MEI);
//        heiHuaList.add(TilesEnum.LAN);
//        heiHuaList.add(TilesEnum.ZHU);
//        heiHuaList.add(TilesEnum.JU);
//        List<TilesEnum> hongHuaList = new ArrayList<>();
//        hongHuaList.add(TilesEnum.CHUN);
//        hongHuaList.add(TilesEnum.XIA);
//        hongHuaList.add(TilesEnum.QIU);
//        hongHuaList.add(TilesEnum.DONG);
//        ownedHuaGroups.add(new TilesGroup(TilesGroupTypeEnum.HEI_HUA, heiHuaList));
//        ownedHuaGroups.add(new TilesGroup(TilesGroupTypeEnum.HONG_HUA, hongHuaList));
//        List<TilesEnum> versatileTiles = new ArrayList<>();
//        versatileTiles.add(TilesEnum.QI_WAN);
//        versatileTiles.add(TilesEnum.BA_WAN);
//        boolean result = canHuaHu(ownedTiles, ownedHuaGroups, versatileTiles);
//        System.err.println(result);


//        List<TilesEnum> ownedTiles = new ArrayList<>();
//        ownedTiles.add(TilesEnum.YI_TIAO);
////        ownedTiles.add(TilesEnum.ER_TIAO);
////        ownedTiles.add(TilesEnum.SAN_TIAO);
//        ownedTiles.add(TilesEnum.SI_WAN);
//        ownedTiles.add(TilesEnum.WU_WAN);
//        ownedTiles.add(TilesEnum.LIU_WAN);
//        ownedTiles.add(TilesEnum.YI_TONG);
//        ownedTiles.add(TilesEnum.ER_TONG);
//        ownedTiles.add(TilesEnum.SAN_TONG);
//        ownedTiles.add(TilesEnum.QI_TONG);
//        ownedTiles.add(TilesEnum.BA_TONG);
//        ownedTiles.add(TilesEnum.JIU_TONG);
//        ownedTiles.add(TilesEnum.WU_WAN);
//        ownedTiles.add(TilesEnum.LIU_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//        List<TilesEnum> versatileTiles = new ArrayList<>();
//        versatileTiles.add(TilesEnum.JIU_WAN);
//        versatileTiles.add(TilesEnum.YI_TIAO);
//        boolean result = canHu(ownedTiles, versatileTiles);
//        System.err.println(result);

//        List<TilesEnum> ownedTiles = new ArrayList<>();
//        ownedTiles.add(TilesEnum.YI_TIAO);
////        ownedTiles.add(TilesEnum.ER_TIAO);
////        ownedTiles.add(TilesEnum.SAN_TIAO);
//        ownedTiles.add(TilesEnum.SI_WAN);
//        ownedTiles.add(TilesEnum.WU_WAN);
//        ownedTiles.add(TilesEnum.LIU_WAN);
//        ownedTiles.add(TilesEnum.YI_WAN);
//        ownedTiles.add(TilesEnum.ER_TONG);
//        ownedTiles.add(TilesEnum.SAN_TONG);
//        ownedTiles.add(TilesEnum.LIU_TIAO);
//        ownedTiles.add(TilesEnum.WU_TONG);
//        ownedTiles.add(TilesEnum.JIU_TONG);
//        ownedTiles.add(TilesEnum.WU_WAN);
//        ownedTiles.add(TilesEnum.YI_TIAO);
//        ownedTiles.add(TilesEnum.YI_TIAO);
//        ownedTiles.add(TilesEnum.JIU_WAN);
////        ownedTiles.add(TilesEnum.JIU_WAN);
//        List<TilesEnum> versatileTiles = new ArrayList<>();
//        versatileTiles.add(TilesEnum.JIU_WAN);
//        versatileTiles.add(TilesEnum.YI_TIAO);
//        boolean result = canShaZhu(ownedTiles, versatileTiles);
//        System.err.println(result);

//        List<TilesEnum> ownedTiles = new ArrayList<>();
//        ownedTiles.add(TilesEnum.DONG_FENG);
//        ownedTiles.add(TilesEnum.DONG_FENG);
//        ownedTiles.add(TilesEnum.DONG_FENG);
//
//        ownedTiles.add(TilesEnum.LIU_WAN);
//        ownedTiles.add(TilesEnum.QI_WAN);
//        ownedTiles.add(TilesEnum.BA_WAN);
//
//        ownedTiles.add(TilesEnum.SAN_TIAO);
//        ownedTiles.add(TilesEnum.SAN_TIAO);
//        ownedTiles.add(TilesEnum.SAN_TIAO);
//
//        ownedTiles.add(TilesEnum.JIU_TIAO);
//        ownedTiles.add(TilesEnum.JIU_TIAO);
//
//        ownedTiles.add(TilesEnum.SI_TONG);
//        ownedTiles.add(TilesEnum.WU_TONG);
//        ownedTiles.add(TilesEnum.LIU_TONG);
//
//        ownedTiles.add(TilesEnum.BA_TONG);
//        ownedTiles.add(TilesEnum.QI_TIAO);
//        ownedTiles.add(TilesEnum.ER_TONG);
//
//        List<TilesEnum> versatileTiles = new ArrayList<>();
//        versatileTiles.add(TilesEnum.QI_TIAO);
//        versatileTiles.add(TilesEnum.ER_TONG);
//        boolean result = canHu(ownedTiles, versatileTiles);
//        System.err.println(result);
    }
}
